"""Definition of the book content type
"""
from zope.interface import implements
from zope.component import adapts

from Products.Archetypes import atapi
from Products.Archetypes.interfaces import IObjectPostValidation 

from Products.ATContentTypes.content import schemata, base
from Products.ATContentTypes.content.schemata import finalizeATCTSchema

from Products.CMFCore.utils import getToolByName

from itx.bookscatalog.interfaces import IBook
from itx.bookscatalog.config import PROJECTNAME

from itx.bookscatalog import bookscatalogMessageFactory as _

# Note: we are using AnnotationStorage for the storage of
# all the fields to avoid conflicts between attribute 
# field names and python propertie names
# (checkout the aspelli's book Professional Plone
# Development for a detail explanation ;)

# Schema for the Book content type
BookSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((

    # It would be nice to have a mutator here to normalize the numbers
    # but the blocks of the ISBN are variable! so later, we can't show the
    # ISBN in a nice way (ie with dashes for the one's without them)
    # Mabye the best is to normalize telling the user he/she must enter 
    # the isbn with dashes (or optionally spaces) as a must.
    atapi.StringField('isbn',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.StringWidget(label=_(u"ISBN number"),
                                description=_(u"ISBN number for the book. Valid Examples:\
                                                0-7914-6518-7, 0-9752298-0-X"))
        ),

    atapi.StringField('authors',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.StringWidget(label=_(u"Authors"),
                                description=_(u"Authors of the book"),
                                size=50)
        ),

    atapi.StringField('summary',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.StringWidget(label=_(u"Short summary"),
                                description=_(u"Short description of the book"))
        ),

    atapi.TextField('teaser',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.TextAreaWidget(label=_(u"Teaser"),
                                description=_(u"A teaser/description of the book"),
                                maxlength=255,
                                rows=6,
                                cols=40,)
        ),

    atapi.StringField('publisher',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.StringWidget(label=_(u"Publisher"),
                                description=_(u"Book's publisher"))
        ),

    atapi.IntegerField('edition',
        requierd=True,
        searcheble=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.IntegerWidget(label=_(u"Edition"),
                                description=_(u"Book's Edition"))
        ),

    atapi.IntegerField('stock',
        required=True,
        searchable=True,
        storage=atapi.AnnotationStorage(),
        widget=atapi.IntegerWidget(label=_(u"Stock"),
                                description=_(u"Availability of this book"))
        ),

    atapi.ImageField('cover',
        required=True,
        languageIndependent=True,
        #storage=atapi.AnnotationStorage(),
        swallowResizeExceptions=True,
        max_size='no',
        sizes={'large'   : (768, 768),
               'preview' : (400, 400),
               'mini'    : (200, 200),
               'thumb'   : (128, 128),
               'tile'    :  (64, 64),
               'icon'    :  (32, 32),
               'listing' :  (16, 16),
               },
        widget=atapi.ImageWidget(label=_(u"Cover"),
                                description=_(u"An image cover for the book"),
                                show_content_type = False,),
        ),
    ))

BookSchema['title'].storage = atapi.AnnotationStorage()
BookSchema['title'].widget.label = _(u"Book name")
BookSchema['title'].widget.description = _(u"")

BookSchema['description'].storage = atapi.AnnotationStorage()
BookSchema['description'].widget.label = _(u"Short blurb")
BookSchema['description'].widget.description = _(u"")

finalizeATCTSchema(BookSchema, folderish=False, moveDiscussion=False)

# Book content type base class
class Book(base.ATCTContent):
    """A book
    """
    implements(IBook)

    portal_type = "Book"
    _at_rename_after_creation = True
    schema = BookSchema
    
    # We make a bridge between fields and python properties,
    # so an external code can access the fields like 
    # python properties (without the accessor/mutator 
    # explicit way)
    isbn = atapi.ATFieldProperty('isbn')
    authors = atapi.ATFieldProperty('authors')
    summary = atapi.ATFieldProperty('summary')
    teaser = atapi.ATFieldProperty('teaser')
    publisher = atapi.ATFieldProperty('publisher')
    edition = atapi.ATFieldProperty('edition')
    stock = atapi.ATFieldProperty('stock')
    book_cover = atapi.ATFieldProperty('cover')  
    # this property is so we have a means to know
    # if the stock is greater than zero, ie, if
    # there is at least one book available
    @property
    def available(self):
        if self.stock > 0:
            return 1
        else:
            return 0
            
atapi.registerType(Book, PROJECTNAME)

# Validation Subscriber for the ISBN field
# this will be called during object validation,
# after the main schema validation has taken place
# Note: common field's validation (a number where it
# is expected a number, etc)is handled by the 
# Archetypes framework, so I don't care about
# that stuff here ;)
class ValidateIsbnUniqueness(object):
    """Validatecorrectness and site-wide
    uniqueness of ISBN's
    """
    implements(IObjectPostValidation)
    adapts(IBook)
    isbn_field_name = 'isbn'
    edition_field_name = 'edition'
    
    def __init__(self, context):
       self.context = context
    
    def isValidIsbn(self, isbn):
        """Check the integrity of an isbn, 10 or 13 digits.
        isbn must be a str object, only chars 0 to 9. Return Boolean.
        """
        isbn_len = len(isbn)
        check_digit = isbn[isbn_len - 1]
        isbn = isbn[:isbn_len - 1]
        if isbn_len == 13:
            if int(check_digit) != ((10 - (sum((1+(i%2)*2)*int(x) for i, x in enumerate(isbn)) % 10))% 10):
                return False # Invalid ISBN
        elif isbn_len == 10:
            if check_digit.lower() == 'x':
                check_digit = 10
            elif check_digit == '0':
                check_digit = 11
            else: check_digit = int(check_digit)
            
            if check_digit != (sum((i+1)*int(x) for i, x in enumerate(isbn)) % 11):
                return False # Invalid ISBN   
        else:
            return False # Invalid length 
        return True # probably valid ISBN :)

    def __call__(self, request):
        isbn = request.form.get(self.isbn_field_name, 
                                request.get(self.isbn_field_name, None))
        edition = request.form.get(self.edition_field_name, 
                                request.get(self.edition_field_name, None))       
        if isbn is not None and edition is not None:
            # First we check the correctness of the ISBN.
            # (we are very strict about this matter!)
            # An ISBN number (edition > 2007) is of the form 
            # ((?=.{13})\d{1,5}([- ])\d{1,7}\1\d{1,6}\1(\d|X)|\d{9}(\d|X))
            # Note: make that a python regexp!
            # I'm tired! so I will only check if there are only digits,
            # dashes and mabye one X|x at the final...without regexp, uggly!,
            # but be aware that this simple verif. doesn't catch all errors.
            for i, c in enumerate(isbn):
                if ("0123456789-Xx ".find(c) == -1) or (c.lower() == "x" and (i != len(isbn) - 1)):
                    return {self.isbn_field_name :
                                _(u"The ISBN entered is not well formed or has invalid characters.\
                                    Characters allowed are: digits from 0 to 9, dashes(-) and x or X.\
                                    Valid 10 digit ISBN example 987-1150-13-X : ")}
            # Strip off the dashes and white space:
            #import pdb; pdb.set_trace()
            isbn = isbn.replace("-", "")
            isbn = isbn.replace(" ", "")
            # length of the number without dashes nor white space
            isbn_len = len(isbn)
            edition = int(edition)
            # We have to take into account the fact that 
            # if the number was assigned before 2007, then
            # it must have 10 digits, else, 13.
            if (isbn_len == 13 and edition > 2006) or (isbn_len == 10 and edition < 2007):
                # check check-digit
                if not self.isValidIsbn(isbn): # bad ISBN
                    return {self.isbn_field_name :
                                _(u"The ISBN entered is invalid (It doesn't acomplish with the ISBN algorithm verification)")}                   
            else: # bad ISBN length
                return {self.isbn_field_name :
                        _(u"Either the ISBN entered doesn't correspond with the edition year\
                            (The ISBN is 13 digits long if assigned after January 1, 2007, and\
                            10 digits long if assigned before 2007) or the length of the number (without dashes) is invalid.")}
                
            # now we check the uniqueness of the ISBN in the catalog
            catalog = getToolByName(self.context, 'portal_catalog')
            results = catalog(isbn=isbn,
                            object_provides=IBook.__identifier__)
           
            if len(results) == 0:
                return None
            elif len(results) == 1 and results[0].UID == self.context.UID():
                return None
            else:
                return {self.field_name :
                        _(u"The book's catalog already has a book with this ISBN")}
        # (no error)
        return None                
